package com.study.es.area.service.impl;

import com.study.es.area.entity.Area;
import com.study.es.area.repository.ElasticAreaRepository;
import com.study.es.area.service.AreaService;
import com.study.es.base.Pages;
import com.study.es.base.constant.PageConstant;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service("areaService")
public class AreaServiceImpl implements AreaService {
	private static final Logger logger = LoggerFactory.getLogger(AreaServiceImpl.class);
    
	@Autowired
	private ElasticAreaRepository elasticAreaRepository;
	@Resource
	private ElasticsearchTemplate elasticsearchTemplate;

	@Override
	public void saveArea(Area area) {
		elasticAreaRepository.save(area);
	}

	@Override
	public void deleteArea(Area area) {
		elasticAreaRepository.delete(area);
	}

	@Override
	public Pages<Area> searchAreaPage(Integer pageNumber, Integer pageSize, String searchContent) {
		// 校验分页参数
        if (pageSize == null || pageSize <= 0) {
            pageSize = PageConstant.PAGE_SIZE;
        }

        if (pageNumber == null || pageNumber < PageConstant.DEFAULT_PAGE_NUMBER) {
            pageNumber = PageConstant.DEFAULT_PAGE_NUMBER;
        }else{
			pageNumber = pageNumber-1;
		}
        // 构建搜索查询
        SearchQuery searchQuery = getLogSearchQuery(pageNumber,pageSize,searchContent);

        logger.info("searchLogPage: searchContent [{}] \n DSL  = \n {}",searchContent,searchQuery.getQuery().toString());

        Page<Area> areaPage = elasticAreaRepository.search(searchQuery);
        Pages<Area> pages = new Pages<Area>();
        pages.setRows(areaPage.getContent());
        pages.setTotal((int)areaPage.getTotalElements());
        pages.setTotalPages(areaPage.getTotalPages());
        return pages;
	}

	/**
	 * 高亮显示，返回分页
	 * @auther: huyuqiang
	 * @date: 2021/04/9 14:32
	 */
	@Override
	public Pages<Area> queryHitByPage(int pageNo, int pageSize, String keyword, String indexName, String... fieldNames) {
		// 构造查询条件,使用标准分词器.
		QueryBuilder matchQuery = createQueryBuilder(keyword,fieldNames);

		// 设置高亮,使用默认的highlighter高亮器
		HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);

		// 设置查询字段
		SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
				.setQuery(matchQuery)
				.highlighter(highlightBuilder)
				.setFrom((pageNo-1) * pageSize)
				.setSize(pageNo * pageSize) // 设置一次返回的文档数量，最大值：10000
				.get();
		// 返回搜索结果
		SearchHits hits = response.getHits();
		Long totalCount = hits.getTotalHits();
		Pages<Area> pages = new Pages<Area>();
		pages.setRows(getHitList(hits));
		pages.setTotal(totalCount.intValue());
		Long totalPages =  totalCount/pageSize;
		pages.setTotalPages(totalPages.intValue());
		return pages;
	}

	/**
	 * 构造查询条件
	 * @auther: huyuqiang
	 * @date: 2021/04/9 14:32
	 */
	private QueryBuilder createQueryBuilder(String keyword, String... fieldNames){
		// 构造查询条件,使用标准分词器.
		return QueryBuilders.multiMatchQuery(keyword,fieldNames)   // matchQuery(),单字段搜索
				.analyzer("ik_max_word")
				.operator(Operator.OR);
	}
	/**
	 * 构造高亮器
	 * @auther: huyuqiang
	 * @date: 2021/04/9 14:32
	 */
	private HighlightBuilder createHighlightBuilder(String... fieldNames){
		// 设置高亮,使用默认的highlighter高亮器
		HighlightBuilder highlightBuilder = new HighlightBuilder()
				// .field("productName")
				.preTags("<span style='color:red'>")
				.postTags("</span>");

		// 设置高亮字段
		for (String fieldName: fieldNames) highlightBuilder.field(fieldName);

		return highlightBuilder;
	}

	/**
	 * 处理高亮结果
	 * @auther: huyuqiang
	 * @date: 2021/04/9 14:32
	 */
	private List<Area> getHitList(SearchHits hits){
		List<Area> list = new ArrayList<>();
		for(SearchHit searchHit : hits){
			Area area = new Area();
			Map<String,Object> map = searchHit.getSourceAsMap();
			area.setId(Long.parseLong(map.get("id").toString()));
			area.setMergerName(map.get("mergerName").toString());
			area.setName(map.get("name").toString());
			area.setShortname(map.get("shortname").toString());
			area.setZipCode(map.get("zipCode").toString());
			area.setLocation(map.get("location").toString());
			// 处理高亮数据
			Map<String,Object> hitMap = new HashMap<>();
			searchHit.getHighlightFields().forEach((k,v) -> {
				String hight = "";
				for(Text text : v.getFragments()) hight += text.string();
				hitMap.put(v.getName(),hight);
			});
			map.put("highlight",hitMap);
			area.setMergerName(hitMap.get("mergerName").toString());
			list.add(area);
		}
		return list;
	}

	 /**
     * 根据搜索词构造搜索查询语句
     * 代码流程：
     *      - 精确查询
     *      - 模糊查询
     *      - 排序查询
     *      - 设置分页参数
     * @param pageNumber 当前页码
     * @param pageSize 每页大小
     * @param searchContent 搜索内容
     * @return
     */
    private SearchQuery getLogSearchQuery(Integer pageNumber, Integer pageSize, String searchContent) {
    	//创建builder
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        /**
         *  must
			所有的语句都 必须（must） 匹配，与 AND 等价。
			must_not
			所有的语句都 不能（must not） 匹配，与 NOT 等价。
			should
			至少有一个语句要匹配，与 OR 等价。
			trem
			精确查找 与= 号等价。
			match
			模糊匹配 与like 等价。
         */
        //设置多字段组合模糊搜索
        if(searchContent!=null && !searchContent.trim().equals("")){
        	builder.must(QueryBuilders.multiMatchQuery(searchContent,"name"));
        }
        //设置排序
        FieldSortBuilder sort = SortBuilders.fieldSort("id").order(SortOrder.DESC);
        //设置分页
        Pageable pageable = new PageRequest(pageNumber, pageSize);
        
        return new NativeSearchQueryBuilder()
                .withPageable(pageable)
                .withQuery(builder)
                .withSort(sort)
                .build();
    }
    
    @Override
	public void getNearbyAreas(double lat, double lon) {
		/**
		 * 通过地理坐标点过滤有四种地理坐标点相关的过滤方式可以用来选中或者排除文档：
		 * geo_bounding_box:: 
		 * 找出落在指定矩形框中的坐标点
		 * geo_distance:: 
		 * 找出与指定位置在给定距离内的点
		 * geo_distance_range:: 
		 * 找出与指定点距离在给定最小距离和最大距离之间的点
		 * geo_polygon:: 
		 * 找出落在多边形中的点。这个过滤器使用代价很大。当你觉得自己需要使用它，最好先看看 geo-shapes
		 */
//		//创建builder
//		QueryBuilder builder = QueryBuilders.geoDistanceRangeQuery("location")
//        		.point(lat,lon)//纬度在前，经度在后
//                .from("0km")
//                .to("400km")
//                .includeLower(true)
//                .includeUpper(false)
//                .optimizeBbox("memory")
//                .geoDistance(GeoDistance.ARC);
//        //字段精确匹配
//	    GeoDistanceSortBuilder sort = new GeoDistanceSortBuilder("location");
//        sort.unit(DistanceUnit.KILOMETERS);//距离单位公里
//        sort.order(SortOrder.ASC);
//        sort.point(lat,lon);//注意纬度在前，经度在后
//        SearchQuery searchQuery   = new NativeSearchQueryBuilder()
//                .withQuery(builder)
//                .withSort(sort)
//                .build();
//        logger.info("getNearbyCities:  DSL  = \n {}",searchQuery.getQuery().toString());
//        Page<Area> areaPage = elasticAreaRepository.search(searchQuery);
//        logger.info("附近地区数量:{}",areaPage.getTotalElements());
//        List<Area> list =  areaPage.getContent();
//        for(Area area:list){
//        	 logger.info("地区名称:{}",area.getName());
//        }
	}

}